<?php
require_once 'Core/User.php';

/**
 * Resource for initializing ACL
 */
class Core_Application_Resource_Acl
	extends Zend_Application_Resource_ResourceAbstract
{
	/**
	 * Internal registry key
	 * 
	 * @var string
	 */
	const DEFAULT_REGISTRY_KEY = 'Zend_Acl';
	
	/**
	 * Internal cache instance used to store our ACL data if such is necessary.
	 * 
	 * @var Zend_Cache_Core
	 */
	private static $cache;
	
	/**
	 * @var Zend_Acl
	 */
	protected $_acl;

	/**
	 * Defined by Zend_Application_Resource_Resource
	 *
	 * @return Zend_Acl
	 */
	public function init()
	{
		$this->initCache();
		$this->initAcl();
		$this->initNavigation();
		return $this->_acl;
	}
	
	/**
	 * This method initializes the access control list, reads it from the registry, or
	 * pulls it from the cache.
	 */
	private function initAcl() 
	{
		if ( self::$cache && self::$cache->test ( self::DEFAULT_REGISTRY_KEY ) )
		{
			$this->_acl = self::$cache->load(self::DEFAULT_REGISTRY_KEY);
		}
		else
		{
			$this->createAcl();
			
			if ( self::$cache )
			{
				self::$cache->save($this->_acl, self::DEFAULT_REGISTRY_KEY);
			}
		}
	}
	
	/**
	 * This method sets up the ACL Cache entry.
	 */
	private function initCache ( )
	{
		$options = $this->getOptions();
		$cacheName = $options['cache'];
		
		if (!empty( $cacheName ) && is_string( $cacheName ))
		{
			$bootstrap = $this->getBootstrap();
			
			if ($bootstrap instanceof Zend_Application_Bootstrap_ResourceBootstrapper && $bootstrap->hasPluginResource('CacheManager') )
			{
				$cacheManager = $bootstrap->bootstrap('CacheManager')->getResource('CacheManager');
				
				if ( $cacheManager !== null && $cacheManager->hasCache( $cacheName ) )
				{
					self::$cache = $cacheManager->getCache( $cacheName );
				}
			}
		}
	}
	
	/**
	 * This method registers our ACL instance with any existing zend navigation resource.
	 */
	private function initNavigation ( )
	{
		Zend_View_Helper_Navigation_HelperAbstract::setDefaultAcl($this->_acl);
		Zend_View_Helper_Navigation_HelperAbstract::setDefaultRole(Core_User::ROLE_GUEST);
	}
	
	/**
	 * Constructs the ACL object from the configuration
	 *
	 * @return Zend_Acl
	 */
	private function createAcl()
	{
		$this->_acl = new Zend_Acl();

		$this->_addRoles();
		$this->_addResources();

		return $this->_acl;
	}
	

	/**
	 * Method used to add our specified roles to our ACL instance.
	 */
	private function _addRoles()
	{
		$options = $this->getOptions();
		
		if (empty($options['roles']))
		{
			return;
		}
		
		// Pull the roles from the configuration
		$roles = $options['roles'];
		
		// This is semi-recursive
		foreach ($roles as $roleName => $properties)
		{
			$this->_addRole ( $roleName, $roles );
		}
	}
	
	/**
	 * Recursive function that creates a role, but makes sure that any
	 * parent roles have already beem created.
	 * 
	 * @param string $id
	 * @param array $roles
	 */
	private function _addRole ( $id, $roles )
	{
		// Check to see if this role already exists
		if ( $this->_acl->hasRole($id) )
		{
			return;
		}
		
		// Let's pull the current role.
		$role = $roles[$id];
		$parents = array();
	
		// Parse through the parents and create them if necessary.
		if (isset($role['parents']) && !empty($role['parents']))
		{
			$parents = explode(',', $role['parents']);	
			for ( $i = 0; $i < count($parents); $i++ )
			{
				if ( !$this->_acl->hasRole($parents[$i]) )
				{
					$this->_addRole($parents[$i],$roles);
				}
			}
		}
		
		$this->_acl->addRole(new Zend_Acl_Role($id), $parents);
	}
	
	/**
	 * Method used to add our specified resources to our ACL instance.
	 */
	private function _addResources()
	{
		$options = $this->getOptions();
		
		if (empty($options['resources']))
		{
			return;
		}
		
		// Pull the roles from the configuration
		$resources = $options['resources'];
		
		// This is semi-recursive
		foreach ($resources as $resourceName => $properties)
		{
			$this->_addResource ( $resourceName, $resources );
		}
	}
	
	/**
	 * Recursive helper functions which creates resources, but makes sure
	 * parent resources are created first.
	 * 
	 * @param string $id
	 * @param array $resources
	 */
	private function _addResource ( $id, $resources )
	{
		// Check to see if this resource already exists
		if ( $this->_acl->has($id) )
		{
			return;
		}
		
		// Let's pull the current role.
		$resource = $resources[$id];
		
		// Pull the parent if it exists.
		if (isset($resource['parent']) && !empty($resource['parent']))
		{
			$parent = $resource['parent'];
			
			if ( !$this->_acl->has($parent) )
			{
				$this->_addResource($parent,$resources);
			}
			
			$this->_acl->add(new Zend_Acl_Resource($id), $parent);
			$this->_addPermissions( $id, $resource );
		}
		else
		{
			$this->_acl->add(new Zend_Acl_Resource($id));
			$this->_addPermissions( $id, $resource );
		}
	}
	
	/**
	 * Adds permissions
	 * 
	 * @param string $resourceId
	 * @param array $resource
	 */
	private function _addPermissions ( $resourceId, $resource )
	{
		if ( isset ( $resource['allow'] ) )
		{
			$allow = $resource['allow'];
			
			foreach ($allow as $role => $rights)
			{
				if ( $role == 'all' )
				{
					$role = null;
				}
				
				$this->_acl->allow($role, $resourceId, explode(',',$rights));
			}
		}
		
		if ( isset ( $resource['deny'] ) )
		{
			$deny = $resource['deny'];
			
			foreach ($deny as $role => $rights)
			{
				if ( $role == 'all' )
				{
					$role = null;
				}
				
				$this->_acl->deny($role, $resourceId, explode(',',$rights));
			}
		}
	}
}